//  This file was created by LevelHelper
//  http://levelhelper.wordpress.com
//
//  LevelHelperLoader.h - online version
//  Created by Bogdan Vladu
//  Copyright 2011 Bogdan Vladu. All rights reserved.
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//  The origin of this software must not be misrepresented; you must not
//  claim that you wrote the original software. If you use this software
//  in a product, an acknowledgment in the product documentation would be
//  appreciated but is not required.
//  Altered source versions must be plainly marked as such, and must not be
//  misrepresented as being the original software.
//  This notice may not be removed or altered from any source distribution.
//  By "software" the author refers to this code file and not the application 
//  that was used to generate this file.
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
//  Version history
//  v1.0 First draft
//  v1.1 Changed the way a level is loaded - now from a plist file 
//	     (removed reference to LevelHelperSprites and LevelHelperJoints)
//  v1.2 Added hasWorldBoundaries and createWorldBoundaries methods
//  v1.3 Fixed issue with release and multiple instances
//  v1.4 add isSpriteWithUniqueNameAtPoint method
//  v1.5 added support for retina display
//  v1.6 better retina display support 
//       convert level functionality
//  v1.7 Fixed a big revolute joint issue
////////////////////////////////////////////////////////////////////////////////////////////////////////



#ifndef __LEVEL_HELPER_LOADER__
#define __LEVEL_HELPER_LOADER__

#include "cocos2d.h"
#include "Box2D/Box2D.h"
#include "map"

using namespace cocos2d;

enum LevelHelper_TAG 
{
	DEFAULT_TAG 	= 0,
	NUMBER_OF_TAGS 	= 1
};

#define BATCH_NODE_CAPACITY 100 //you should change this value if you have more then 100 sprites in a texture image

//Pixel to metres ratio. Box2D uses metres as the unit for measurement.
//This ratio defines how many pixels correspond to 1 Box2D "metre"
//Box2D is optimized for objects of 1x1 metre therefore it makes sense
//to define the ratio so that your most common object type is 1x1 metre.
#define PTM_RATIO 32 //you should only keep this here so you don't change it everywhere - so you should delete it from everywhere else

typedef std::map<std::string, std::string> LHDictionary;
class JointInfo;
class SpriteInfo;
class ImageInfo;

class LevelHelperLoader
{
	
public:
	
	/**
	 Constructor - create a new LevelHelperLoader object\n
	 
	 @param[in] levelFile - a string representing the level file name 
	 
	 @code 
	 LevelHelperLoader* loader = new LevelHelperLoader("Level1.lhs");
	 @endcode
	 */
	LevelHelperLoader(const std::string& levelFile);
    
	///////////////////////////////////////////////////////////////////////////////////////////////
	
    //this will convert level to the used devide - safe frame must be the size of the
    //device resolution inside LevelHelper - by default convertLevel is TRUE
    //only call this method do deactivate the convert level option
    void setConvertLevel(bool value);

    
	virtual ~LevelHelperLoader();
	
	/**
	 Overwrite this function to create your own custom CCSprite\n
	 See http://levelhelper.wordpress.com/use-derived-ccsprite/ for more details;
	 */
	virtual CCSprite* spriteFromMap(LHDictionary &spriteMap);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	/**
	 Overwrite this function to create your own custom CCSprite\n
	 See http://levelhelper.wordpress.com/use-derived-ccsprite/ for more details;
	 */
	virtual CCSprite* spriteWithBatchFromMap(LHDictionary &spriteMap, 
											 CCSpriteBatchNode* batch);

	
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	/**
	 Use this method to add your sprites into your project and also create all the physic bodies\n
	 This function is used when you want physic for your level\n
	 Can't be used together with addSpritesToLayer\n
	 <b>THIS SHOULD ONLY BE USED ONCE</b>\n
	 Box2d objects will have as .userData a CCSprite Object\n
	 CCSprite object will have no user data\n
	 Cocos2D layer is populated with CCSprites.\n
	 
	 @param[in] world - b2World instance for Box2d
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 
	 @code 
	 loader->addObjectsToWorld(world,this);
	 @endcode
	 
	 \b CAREFUL - you should start your own batchNode's tags numbering with a number bigger then the value returned
	 by method numberOfBatchNodesUsed
	 */
	void addObjectsToWorld(b2World* world, CCLayer* cocosLayer);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	/**
	 Use this method to add your sprites into your project.\n
	 This function is used when you don't want physic for your level.\n
	 Can't be used together with addObjectsToWorld.\n
	 <b>THIS SHOULD ONLY BE USED ONCE</b>\n
	 CCSprite object will have no user data\n
	 Cocos2D layer is populated with CCSprites.\n
	 
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 
	 @code 
	 loader->addSpritesToLayer(this);
	 @endcode
	 
	 \b CAREFUL - you should start your own batchNode's tags numbering with a number bigger then the value returned
	 by method numberOfBatchNodesUsed
	 */
	void addSpritesToLayer(CCLayer* cocosLayer);
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to test in the code if you have world boundaries created in the loaded level\n
	 */
	bool hasWorldBoundaries();
	///////////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to create the world boundaries from the current loaded level\n
	 	 
	 @param[in] world - the b2World instance
	 */
	void createWorldBoundaries(b2World* world);
	///////////////////////////////////////////////////////////////////////////////////////////////////
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to get the number of batch nodes that were created when loading the level.\n
	 Create your batch node with a number bigger than this value.\n
	 If you don't do this - all your sprites textures will be overwriten with what you create.\n
	 */
	unsigned int numberOfBatchNodesUsed(void);
	

	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to test if a user clicked on a particular sprite - useful for menus\n

	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
     @param[in] point - point on screen representing click location
	 @return bool - true if clicked on sprite false otherwise
	 @code 
	 if( loader->isSpriteWithUniqueNameAtPoint("Stone64x32", cc(10, 10))
     {
     //do something
     }
	 @endcode
	 */    
    bool isSpriteWithUniqueNameAtPoint(const std::string& uniqueName, CCPoint point);
    
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to get a pointer to the CCSprite that was created when loading the level with addObjectsToWorld or addSpritesToLayer.\n
	 Use this method to add your own userData or change the information contained in the CCSprite object returned.\n
	 
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @return CCSprite* or NULL if no sprite with unique name given is found
	 @code 
	 CCSprite* sprite = loader->spriteWithUniqueName("Stone64x32");
	 sprite->setScaleY(2);
	 @endcode
	 */
	CCSprite* spriteWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to get a pointer to the b2Body that was created when loading the level with addObjectsToWorlds.\n
	 
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @return b2Body* or NULL if no sprite with unique name given is found
	 
	 @code
	 loader->bodyWithUniqueName("Stone64x32")
	 @endcode
	 */
	b2Body* bodyWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to create a new CCSprite with the information from the LevelHelper sprite with the unique name given.\n
	 
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 @return CCSprite* or NULL if no sprite with unique name given is found
	 @code
	 CCSprite* sprite = loader->newSpriteWithUniqueName("Stone64x32", this);
	 @endcode
	 */
	CCSprite* newSpriteWithUniqueName(const std::string& uniqueName, CCLayer* cocosLayer);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to create a new b2Body with the information from the LevelHelper sprite with the unique name given.\n
	 The b2Body returned will have as userData a CCSprite object.\n
	 
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @param[in] world - the b2World instance in which you want to add the b2Body
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 
	 @return  b2Body* or NULL if no sprite with unique name given is found
	 
	 @code
	 b2Body* body = loader->newBodyWithUniqueName("Basketball", world, this);
	 @endcode
	 */
	b2Body* newBodyWithUniqueName(const std::string& uniqueName, b2World* world, CCLayer* cocosLayer);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to get a vector containing pointers to all CCSprite objects that have the specified tag that were created when loading the level with addObjectsToWorld or addSpritesToLayer.\n
	 Use this method to add your own userData or change the information contained in the CCSprite object's returned.\n
	 
	 @param[in] tag - a tag that is found inside LevelHelper_TAG enum and is definde inside LevelHelper with Create tag's
	 @return vector containing CCSprite* or empty vector if no sprite with the given tag is found
	 @code
	 std::vector<CCSprite*> spr = loader->spritesWithTag(WOOD);
	 for(int i = 0; i < spr.size(); ++i)
	 {
	 CCSprite* sprite = spr[i];
	 sprite->setScaleY(2);
	 }
	 @endcode
	 */
	std::vector<CCSprite*> spritesWithTag(LevelHelper_TAG tag);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	/**
	 Use this method to get a vector containing pointers to all b2Body objects that have the specified tag that were created when loading the level with addObjectsToWorld.\n
	 Use this method to add your own userData or change the information contained in the b2Body object's returned.\n
	 b2Body's will have as userData a CCSprite object.\n
	 @param[in] tag - a tag that is found inside LevelHelper_TAG enum and is definde inside LevelHelper with Create tag's
	 @return vector containing b2Body* or empty vector if no sprite with the given tag is found
	 @code
	 std::vector<b2Body*> bodies = loader->bodiesWithTag(WOOD);
	 for(int i = 0; i < bodies.size(); ++i)
	 {
	 b2Body* body = bodies[i];
	 body->SetTransform(b2Vec2(50, 50), 0);
	 }
	 @endcode
	 */
	std::vector<b2Body*> bodiesWithTag(LevelHelper_TAG tag);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to create new CCSprite objects with the information from the LevelHelper sprites that have the tag given.\n
	 
	 @param[in] tag - a tag that is found inside LevelHelper_TAG enum and is definde inside LevelHelper with Create tag's
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 
	 @return vector containing CCSprite* or empty vector if no sprite with the given tag is found.
	 
	 @code
	 std::vector<CCSprite*> spr = loader->newSpritesWithTag(WOOD, this);
	 for(int i = 0; i < spr.size(); ++i)
	 {
	 CCSprite* sprite = spr[i];
	 sprite->setScaleY(2);
	 sprite->setPosition(CCPointMake(250, 250));
	 }
	 @endcode
	 */
	std::vector<CCSprite*> newSpritesWithTag(LevelHelper_TAG tag, CCLayer* cocosLayer);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	/**
	 Use this method to create new b2Body* objects with the information from the LevelHelper sprites that have the tag given.\n
	 b2Body's will have as UserData a CCSprite object.\n
	 @param[in] tag - a tag that is found inside LevelHelper_TAG enum and is definde inside LevelHelper with Create tag's
	 @param[in] world - the b2World instance in which you want to add the b2Body's
	 @param[in] cocosLayer - the class that inherits from CCLayer (Cocos2d template creates this class as HelloWorldScene)
	 
	 @return vector containing b2Body* or empty vector if no sprite with the given tag is found.
	 
	 @code
	 std::vector<b2Body*> bodies = loader->newBodiesWithTag(WOOD, world, this);
	 @endcode
	 */
	std::vector<b2Body*> newBodiesWithTag(LevelHelper_TAG tag, b2World* world, CCLayer* cocosLayer);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a CCSprite created with LevelHelperLoader from your project.\n
	 <b>USE THIS METHOD ONLY WITH SPRITES CREATED WITH</b> the method addSpritesToLayer.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> CCSprite's created with newCCSpriteWithUniqueName or newCCSpritesWithTag.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> CCSprite's created with addObjectToWorld.\n
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @return true if the CCSprite was removed, false otherwise
	 @code
	 loader->removeSpriteWithUniqueName("Basketball");
	 @endcode
	 */
	bool removeSpriteWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a CCSprite created with LevelHelperLoader from your project.\n
	 <b>USE THIS METHOD ONLY WITH SPRITES CREATED WITH</b> the method addSpritesToLayer.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> CCSprite's created with newCCSpriteWithUniqueName or newCCSpritesWithTag.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> CCSprite's created with addObjectToWorld.\n
	 @param[in] ccsprite - pointer to a CCSprite* object created with addSpritesToLayer
	 @return true if the CCSprite was removed, false otherwise
	 
	 @code
	 loader->removeSprite( loader->spriteWithUniqueName("Stone64x32") );
	 @endcode
	 */
	bool removeSprite(CCSprite* ccsprite);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a b2Body created with LevelHelperLoader from your project.\n
	 <b>USE THIS METHOD ONLY WITH b2Body's CREATED WITH</b> the method addObjectToWorld.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> b2Body's created with newBodyWithUniqueName or newBodiesWithTag.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> b2Body's created with addSpritesToLayer.\n
	 @param[in] uniqueName - a string representing the unique name given to a sprite by LevelHelper
	 @return true if the b2Body was removed, false otherwise
	 @code
	 loader->removeBodyWithUniqueName("Stone64x32");
	 @endcode
	 */
	bool removeBodyWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a b2Body created with LevelHelperLoader from your project.\n
	 <b>USE THIS METHOD ONLY WITH b2Body's CREATED WITH</b> the method addObjectToWorld.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> b2Body's created with newBodyWithUniqueName or newBodiesWithTag.\n
	 <b>YOU CANNOT USE THIS METHOD WITH</b> b2Body's created with addSpritesToLayer.\n
	 @param[in] body - pointer to a b2Body* object created with addObjectToWorld
	 @return true if the b2Body was removed, false otherwise
	 
	 @code
	 loader->removeBody( loader->bodyWithUniqueName("Stone64x32") );
	 @endcode
	 */
	bool removeBody(b2Body* body);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to get a pointer to a b2Joint that was created when loading the level with addObjectToWorld.\n
	 @param[in] uniqueName - a string representing the joint name given by LevelHelper
	 @return a b2Joint* or NULL if no joint is found with the given name
	 @code
	 loader->jointWithUniqueName("Stone32x32_DiamondBlue")
	 @endcode
	 */
	b2Joint* jointWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a joint from the scene
	 
	 @param[in] uniqueName - a string representing the joint name given by LevelHelper
	 @return true if the b2Joint was removed, false otherwise
	 @code
	 loader->removeJointWithUniqueName("Stone32x32_DiamondBlue");
	 @endcode
	 */
	bool removeJointWithUniqueName(const std::string& uniqueName);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 Use this method to remove a joint from the scene
	 @param[in] joint - a b2Joint* from your scene
	 @return true if the b2Joint was removed, false otherwise
	 
	 @code
	 loader->removeJoint( loader->jointWithUniqueName("Stone32x32_DiamondBlue") );
	 @endcode
	 */
	bool removeJoint(b2Joint* joint);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	/**
	 This method is used when creating your own custom CCSprite's object.\n
	 See http://levelhelper.wordpress.com/use-derived-ccsprite/ for more details;
	 */
	void setSpritePropertiesFromMap(CCSprite* ccsprite, LHDictionary &spriteMap);
	
	///////////////////////////////////////////////////////////////////////////////////////////////
private:
	
	LevelHelperLoader(){}
	
	std::vector<SpriteInfo*> lhSprites;
	std::vector<JointInfo*> lhJoints;
	std::vector<ImageInfo*> lhImages;
	std::string wb;
	
	std::map<std::string, CCSprite*>			ccSpritesInScene;
	std::map<std::string, b2Body*>				ccBodiesInScene;
	std::map<std::string, b2Joint*>				ccJointsInScene;
	
	b2World* world;
	CCLayer* cocosLayer;

    
    bool convertLevel;
    CCPoint convertRatio;
    
	struct batchInfo
	{
		CCSpriteBatchNode* batch;
		int orderZ;
	};
	
	std::map<std::string, batchInfo*> batchNodes;
	
	bool addSpritesToLayerWasUsed;
	bool addObjectsToWordWasUsed;	
	
	CCRect worldBoundaries;
	
	void addBatchNodesToLayer(CCLayer* cocosLayer);
	
	void setFixtureDefPropertiesFromMap(LHDictionary& physic,
										b2FixtureDef *shapeDef);

    
	
	b2Body* b2BodyFromMap(LHDictionary& properties,
						  std::vector<std::vector<std::string> >&fixtures,
						  CCSprite* ccsprite,
						  b2World* world);
	
	b2Joint* b2JointFromMap(std::map<std::string, std::string>& properties, b2World* world);
	
	void releaseAll();
	
	void key(const std::string& arg);
	void pushValue(const std::string& arg);
	void arrayBegin();
	void noArray();
	void arrayEnd();
	void dictBegin();
	void dictEnd();
	std::string valueForField(const std::string& field);
	
	void loadLevelHelperSceneFile(const std::string& levelFile);
	
	void assertMessage(bool value, const std::string message);
};

#endif


